package cn.js189.uqc.common;

import cn.hutool.extra.spring.SpringUtil;
import cn.js189.common.constants.ErrorConstants;
import cn.js189.common.constants.HttpConstant;
import cn.js189.common.constants.InvoiceConstant;
import cn.js189.common.util.HttpUtil;
import cn.js189.common.util.exception.BaseAppException;
import cn.js189.common.util.exception.ExceptionUtil;
import cn.js189.common.util.helper.DateHelper;
import cn.js189.common.util.helper.UUIDHelper;
import cn.js189.uqc.domain.*;
import cn.js189.uqc.domain.invoice.InvoiceAuthDTO;
import cn.js189.uqc.domain.invoice.InvoiceDetail;
import cn.js189.uqc.domain.invoice.InvoiceInsert;
import cn.js189.uqc.mapper.CheckInfoMapper;
import cn.js189.uqc.redis.RedisOperation;
import cn.js189.uqc.service.InvoiceService;
import cn.js189.uqc.util.DateUtils;
import cn.js189.uqc.util.EInvoiceUtil;
import cn.js189.uqc.util.HttpClientUtils;
import cn.js189.uqc.util.RedisUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jetty.util.MultiMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Date;
import java.util.Objects;

@Component
public class WxService {

	private static final Logger LOGGER = LoggerFactory.getLogger(WxService.class);

	@Resource
	private RedisOperation redisOperation;

	@Resource
	private CheckInfoMapper checkInfoMapper;

	@Resource
	private InvoiceService invoiceService;

	private static final boolean USE_PROXY = Boolean.TRUE;

	private static final String PROXY_FLAG = "Y";

	private static final String ERRCODE = "errcode";

	private static final String ACCESSTOKEN = "access_token";

	/**
	 * redis 微信access_token key
	 */
	private static final String WX_ACCESS_TOKEN_KEY = "WX_ACCESS_TOKEN_KEY";

	/**
	 * redis 微信access_token key有效期
	 */
	private static final int WX_KEY_TIME = 7100;

	/**
	 * redis 微信access_token key 锁
	 * 防止多应用同时获取token导致token不确定
	 */
	private static final String WX_ACCESS_TOKEN_LOCK_KEY = "WX_ACCESS_TOKEN_LOCK_KEY";

	/**
	 * redis 获取授权页ticket key锁
	 */
	private static final String WX_TICKET_LOCK_KEY = "WX_TICKET_LOCK_KEY";

	/**
	 * 自有服务分配的 app_id
	 */
	private static final String APP_ID = "wa0b24abd1334ac1b7";


	/**
	 * 自有服务分配的app_secret
	 */
	private static final String APP_SECRET = "78475c52e30ed00567cd1bb8cc832bbd";

	/**
	 * 公众号app_id
	 */
	private static final String APP_ID_ID = "wxb36309f36a5662aa";

	/**
	 * 开票超时时间
	 * 10分钟
	 */
	private static final int CONTACT_TIMEOUT = 90000;

	/**
	 * 微信侧获取access_token url
	 */
	private static final String ACCESS_TOKEN_URL = "/wxapi/cgi-bin/token?grant_type=client_credential&appid=" + APP_ID + "&secret=" + APP_SECRET;

	/**
	 * 获取自身的开票平台识别码s_pappid
	 */
	private static final String SPAPPID_URL = "/wxapi/card/invoice/seturl?access_token=";

	/**
	 * 获取授权页ticket
	 */
	private static final String GET_TICKET_URL = "/wxapi/cgi-bin/ticket/getticket?type=wx_card&access_token=";

	/**
	 * 微信侧直接获取access_token
	 * 有效期 7200秒
	 */
	public String getWxAccessToken() throws BaseAppException {

		String accessToken = this.redisOperation.getForString(WX_ACCESS_TOKEN_KEY);

		if (StringUtils.isEmpty(accessToken)) {

			String lockValue = UUIDHelper.getUUID();
			String lockResult = RedisUtil.tryLock(redisOperation, "获取access_token", WX_ACCESS_TOKEN_LOCK_KEY, lockValue);

			// 获取锁失败
			if (!StringUtils.equals(lockResult, RedisUtil.LOCK_SUCCESS)) {
				ExceptionUtil.throwBusiException(ErrorConstants.WX_LOCK_ACCESS_TOKEN_FAIL);
			}

			NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
			// 获取锁成功
			LOGGER.info("获取微信access_token URL :{}", nacosCommon.getProxyUrl()+ACCESS_TOKEN_URL);
			String result = EInvoiceUtil.sendRequestWithHead(nacosCommon.getProxyUrl()+ACCESS_TOKEN_URL, "", USE_PROXY, "GET", null);
			LOGGER.info("获取微信access_token 出参 :{}", result);

			if (StringUtils.isEmpty(result)) {
				ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_ACCESS_TOKEN_FAIL);
			}

			JSONObject resObj = JSON.parseObject(result);
			accessToken = resObj.getString(ACCESSTOKEN);

			if (StringUtils.isEmpty(accessToken)) {
				ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_ACCESS_TOKEN_FAIL);
			}

			this.redisOperation.setEx(WX_ACCESS_TOKEN_KEY, WX_KEY_TIME, accessToken);

			RedisUtil.unlock(redisOperation, "获取access_token", WX_ACCESS_TOKEN_LOCK_KEY, lockValue);
		}

		return accessToken;
	}

	/**
	 * redis 微信自有服务ticket key
	 * 用于调用自有服务接口获取access_token
	 */
	private static final String WX_SEL_ACCESS_TOKEN_TICKETKEY = "WX_SEL_ACCESS_TOKEN_TICKETKEY";

	/**
	 * 自有服务获取access_token_ticket
	 * 用这个ticket调用自有服务获取token接口获取最终微信侧access_token
	 * 测试地址
	 */
	private static final String GET_ACCESS_TOKEN_TICKET_URL = "http://wxht.telecomjs.com:8081/api/wa/agent/token?appid=" + APP_ID + "&secret=" + APP_SECRET;

	/**
	 * 自有服务获取access_token
	 */
	private static final String GET_ACCESS_TOKEN_URL = "http://wxht.telecomjs.com:8081/api/wa/token?asToken=";

	/**
	 * 通过自有服务接口获取微信access_token
	 */
	public String getSelWxAccessToken() throws BaseAppException {

		// 根据source不同获取token？
		String accessToken = "";
		String ticket = this.redisOperation.getForString(WX_SEL_ACCESS_TOKEN_TICKETKEY);

		if (StringUtils.isEmpty(ticket)) {
			LOGGER.info("自有服务获取微信access_token_ticket URL:{}", GET_ACCESS_TOKEN_TICKET_URL);
			String res = HttpClientUtils.sendGet(GET_ACCESS_TOKEN_TICKET_URL, null);
			LOGGER.info("自有服务获取微信access_token_ticket出参:{}", res);

			if (StringUtils.isEmpty(res)) {
				ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_ACCESS_TOKEN_TICKT_FAL);
			}

			JSONObject resObj = JSON.parseObject(res);
			ticket = resObj.getString(ACCESSTOKEN);

			if (StringUtils.isEmpty(ticket)) {
				ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_ACCESS_TOKEN_TICKT_FAL);
			}

			int expiresIn = resObj.getIntValue("expires_in");
			this.redisOperation.setEx(WX_SEL_ACCESS_TOKEN_TICKETKEY, (Math.max(expiresIn, 0)), ticket);

		}

		LOGGER.info("自有服务ticket:{}", ticket);

		// 通过自有服务ticket获取微信access_token
		String url = GET_ACCESS_TOKEN_URL + ticket;
		LOGGER.info("自有服务ticket获取微信access_token URL:{}", url);
		String res = HttpClientUtils.sendGet(url, null);

		LOGGER.info("通过自有服务ticket获取微信access_token出参:{}", res);

		if (!StringUtils.isEmpty(res)) {
			JSONObject object = JSON.parseObject(res);
			accessToken = object.getString(ACCESSTOKEN);

			if (StringUtils.isEmpty(accessToken)) {
				ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_ACCESS_TOKEN_FAIL);
			}

		} else {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_ACCESS_TOKEN_FAIL);
		}

		return accessToken;
	}

	/**
	 * 获取微信自身的开票平台识别码 s_pappid
	 * 操作只需要一次,同一开票平台 s_pappid相同
	 */
	public String getSPappid() throws BaseAppException {

		String sPappid = this.checkInfoMapper.selectSPappidByAppidAndAppsecret(APP_ID, APP_SECRET);
		LOGGER.info("数据库读取商户s_pappid: {}", sPappid);

		// 已经有s_pappid记录
		if (!StringUtils.isEmpty(sPappid)) {
			return sPappid;
		}

		// 获取access_token
		String accessToken = getSelWxAccessToken();
		NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
		String url = nacosCommon.getProxyUrl()+SPAPPID_URL + accessToken;
		LOGGER.info("获取商户s_pappid URL: {}", url);

		String res = EInvoiceUtil.postJSONHttpProxy(url, "{}", HttpConstant.CONNECT_TIMEOUT, PROXY_FLAG);
		LOGGER.info("获取商户s_pappid出参:{}", res);

		if (StringUtils.isEmpty(res)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_S_PAPPID_FAIL);
		}

		JSONObject resObj = JSON.parseObject(res);
		String errCode = resObj.getString(ERRCODE);

		if (!StringUtils.equals("0", errCode)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_S_PAPPID_FAIL);
		}

		// 获取成功
		String invoiceUrl = resObj.getString("invoice_url");
		LOGGER.info("开票平台专用的授权链接:{}", invoiceUrl);

		// 解析url中的 s_pappid
		MultiMap<String> values = EInvoiceUtil.urlParamResolver(invoiceUrl);
		if (!values.isEmpty()) {
			sPappid = values.getString("s_pappid");
		}

		LOGGER.info("自身开票平台s_pappid:{}", sPappid);

		if (StringUtils.isEmpty(sPappid)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_S_PAPPID_FAIL);
		}

		// s_pappid入库
		this.checkInfoMapper.updatesPappid(sPappid, APP_ID, APP_SECRET);

		return sPappid;

	}

	/**
	 * 设置商户联系方式url
	 */
	private static final String SET_BIZ_ATTR_URL = "/wxapi/card/invoice/setbizattr?action=set_contact&access_token=";


	/**
	 * 设置商户联系方式
	 */
	public void setBizAttr() throws BaseAppException {

		String phone = this.checkInfoMapper.selectContactPhone(APP_ID, APP_SECRET);

		// 获取access_token
		String accessToken = getSelWxAccessToken();

		NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
		String url = nacosCommon.getProxyUrl()+SET_BIZ_ATTR_URL + accessToken;
		LOGGER.info("设置商户联系方式 URL: {}", url);

		JSONObject param = new JSONObject();
		JSONObject contact = new JSONObject();
		contact.put("phone", phone);
		contact.put("time_out", CONTACT_TIMEOUT);
		param.put("contact", contact);

		LOGGER.info("设置商户联系方式入参: {}", param);
		String res = EInvoiceUtil.postJSONHttpProxy(url, param.toString(), HttpConstant.CONNECT_TIMEOUT, PROXY_FLAG);
		LOGGER.info("设置商户联系方式出参: {}", res);

		if (StringUtils.isEmpty(res)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_SET_CONTACT_FAIL);
		}

		JSONObject resObj = JSONObject.parseObject(res);
		String errCode = resObj.getString(ERRCODE);

		if (!StringUtils.equals("0", errCode)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_SET_CONTACT_FAIL);
		}

		LOGGER.info("设置商户联系方式成功");

	}

	/**
	 * 查询商户联系方式
	 */
//	private static final String GET_BIZ_ATTR_URL = "https://api.weixin.qq.com/card/invoice/setbizattr?action=get_contact&access_token=";
	private static final String GET_BIZ_ATTR_URL = "/wxapi/card/invoice/setbizattr?action=get_contact&access_token=";


	public void getBizAttr() throws BaseAppException {
		// 获取access_token
		String accessToken = getSelWxAccessToken();

		NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
		String url = nacosCommon.getProxyUrl()+GET_BIZ_ATTR_URL + accessToken;
		LOGGER.info("获取商户联系方式 URL: {}", url);

		String res = EInvoiceUtil.postJSONHttpProxy(url, "{}", HttpConstant.CONNECT_TIMEOUT, PROXY_FLAG);
		LOGGER.info("获取商户联系方式 出参: {}", res);
	}


	/**
	 * redis 微信授权页ticket key
	 */
	private static final String WX_TICKET_KEY = "WX_TICKET_KEY";

	/**
	 * 获取商户获取授权页ticket
	 * 有效期 7200秒
	 */
	public String getTicket() throws BaseAppException {

		String ticket = this.redisOperation.getForString(WX_TICKET_KEY);

		if (!StringUtils.isEmpty(ticket)) {
			return ticket;
		}

		// 获取access_token
		String accessToken = getSelWxAccessToken();

		NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
		String url = nacosCommon.getProxyUrl()+GET_TICKET_URL + accessToken;
		LOGGER.info("获取授权页ticket URL: {}", url);

		String lockValue = UUIDHelper.getUUID();
		String lockResult = RedisUtil.tryLock(redisOperation, "授权页ticket锁", WX_TICKET_LOCK_KEY, lockValue);

		// 获取锁失败
		if (!StringUtils.equals(lockResult, RedisUtil.LOCK_SUCCESS)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_LOCK_ACCESS_TOKEN_FAIL);
		}

		String res = EInvoiceUtil.sendRequestWithHead(url, "", USE_PROXY, "GET", null);
		LOGGER.info("获取授权页ticket 出参: {}", res);

		if (StringUtils.isEmpty(res)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_TICKET_FALIL);
		}

		JSONObject resObj = JSON.parseObject(res);
		String errCode = resObj.getString(ERRCODE);

		if (!StringUtils.equals("0", errCode)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_TICKET_FALIL);
		}

		ticket = resObj.getString("ticket");

		this.redisOperation.setEx(WX_TICKET_KEY, WX_KEY_TIME, ticket);

		RedisUtil.unlock(redisOperation, "授权页ticket", WX_TICKET_LOCK_KEY, lockValue);

		return ticket;
	}

	/**
	 * 获取授权页url
	 */
//	private static final String WX_GET_AUTHURL = "https://api.weixin.qq.com/card/invoice/getauthurl?access_token=";
	private static final String WX_GET_AUTHURL = "/wxapi/card/invoice/getauthurl?access_token=";

	/**
	 * 获取授权页url (单张发票单次授权)
	 * 微信侧入参:
	 * s_pappid:商户号
	 * order_id:订单号,此处用ticketId
	 * money:金额 单位分
	 * timestamp:时间戳
	 * source:开票涞源 app：app开票，web：微信h5开票，wxa：小程序开发票，wap：普通网页开票
	 * redirect_url:授权成功后跳转页面 h5时填写
	 * ticket:微信侧授权页ticket
	 *
	 * @param redirectUrl 授权后跳转页面
	 */
	public JSONObject getAuthUrl(String ticketId, String invoiceId, String money, String source, String type, String redirectUrl) throws BaseAppException {

		// 获取access_token
		String accessToken = getSelWxAccessToken();

		// 获取商户开票平台标识
		String sPappid = getSPappid();

		// 设置商户联系方式
		setBizAttr();

		// 获取授权页ticket
		String ticket = getTicket();

		// 获取授权页url
		NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
		String url = nacosCommon.getProxyUrl()+WX_GET_AUTHURL + accessToken;
		LOGGER.info("获取授权页请求 URL: {}", url);

		String param = "";

		// 申请开票类型授权
		if (StringUtils.equals("0", type)) {
			// orderId为ticket
			param = new WxAuthUrlDTO(sPappid, ticketId, money, source, type, redirectUrl, ticket).toString();
		}

		// 领取发票类型授权
		if (StringUtils.equals("2", type)) {
			// orderId为invoiceId
			param = new WxAuthUrlDTO(sPappid, invoiceId, money, source, type, redirectUrl, ticket).toString();
		}

		LOGGER.info("获取授权页链接 入参: {}", param);
		String res = HttpUtil.postJSONHttpProxy(url, param, HttpConstant.CONNECT_TIMEOUT, PROXY_FLAG);
		LOGGER.info("获取授权页链接 出参: {}", res);

		if (StringUtils.isEmpty(res)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_AUTH_URL_FALIL);
		}

		JSONObject resObj = JSON.parseObject(res);
		String errCode = resObj.getString(ERRCODE);

		if (!StringUtils.equals("0", errCode)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_AUTH_URL_FALIL);
		}

		JSONObject authInfo = new JSONObject();

		String authUrl = resObj.getString("auth_url");
		authUrl = authUrl.replaceAll("\\\\", "");

		authInfo.put("authUrl", authUrl);
		authInfo.put("ticket", ticketId);

		// 小程序会有appid返回
		String appId = resObj.getString("appid");
		authInfo.put("appId", appId);

		// 同一张发票不同source获取授权页链接,需不需要保存信息？
		// 同样的invoiceId申请的授权获取的授权页url是一致的,先要判断是否已经授权,如果已经授权就不需要插入数据
		InvoiceAuthDTO invoiceAuthDTO = this.checkInfoMapper.selectInvoiceAuthInfoByInvoiceIdAndSource(invoiceId, source);
		if (null == invoiceAuthDTO) {
			// 获取到授权页url默认未授权状态
			InvoiceAuthDTO invoiceAuthInfo = new InvoiceAuthDTO(ticketId, invoiceId, money, source, type, redirectUrl, "0");
			this.checkInfoMapper.insertInvoiceAuthInfo(invoiceAuthInfo);
			LOGGER.info("获取授权页完成,待授权发票信息保存成功!");
		}

		LOGGER.info("获取授权页完成");

		return authInfo;
	}


	/**
	 * 一次授权,多次插卡url
	 */
//	private static final String WX_GET_BATCH_AUTHURL = "https://api.weixin.qq.com/card/invoice/getbatchinsertauthurl?access_token=";
	private static final String WX_GET_BATCH_AUTHURL = "/wxapi/card/invoice/getbatchinsertauthurl?access_token=";

	/**
	 * 获取一次授权,多次插卡授权页
	 */
	public JSONObject getBatchAuthUrl(String appId, String partyId, String source, String redirectUrl) throws BaseAppException {

		// 获取access_token
		String accessToken = getSelWxAccessToken();

		NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
		String url = nacosCommon.getProxyUrl()+WX_GET_BATCH_AUTHURL + accessToken;
		LOGGER.info("一次授权多次插卡请求 URL: {}", url);

		JSONObject param = new JSONObject();
		param.put("source", source);
		param.put("out_user_id", partyId);  // 商户侧用户唯一标识

		// 当source为app时，该参数为必填，填写对应的app账号的appid
		if (StringUtils.equals("app", source)) {
			param.put("app_appid", appId);

		}else if (StringUtils.equals("wxa", source)) {
			// 当source为wxa时，该参数为必填，填写对应的小程序账号的appid
			param.put("wxa_appid", appId);

		}else if (StringUtils.equals("web", source)) {
			// 授权后跳转链接，只有source为web的时候有效
			param.put("redirect_url", redirectUrl);

		}

		LOGGER.info("一次授权多次插卡请求入参: {}", param);
		String res = HttpUtil.postJSONHttpProxy(url, param.toString(), HttpConstant.CONNECT_TIMEOUT, PROXY_FLAG);
		LOGGER.info("一次授权多次插卡请求出参: {}", res);

		if (StringUtils.isEmpty(res)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_BATCHAUTH_URL_FALIL);
		}

		JSONObject resObj = JSON.parseObject(res);
		String errCode = resObj.getString(ERRCODE);

		if (!StringUtils.equals("0", errCode)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_BATCHAUTH_URL_FALIL);
		}

		String authUrl = resObj.getString("auth_url");
		String authKey = resObj.getString("auth_key");
		authUrl = authUrl.replace("\\\\", "");

		// 授权失效时间
		int expireTime = resObj.getIntValue("expire_time");

		JSONObject authInfo = new JSONObject();
		authInfo.put("authUrl", authUrl);
		authInfo.put("authKey", authKey);   // 小程序授权用
		authInfo.put("expireTime", expireTime);

		return authInfo;

	}

	/**
	 * 微信侧获取用户一次授权信息 URL
	 */
//	private static final String WX_GET_BATCH_AUTH_INFO = "https://api.weixin.qq.com/card/invoice/getbatchinsertauthinfo?access_token=";
	private static final String WX_GET_BATCH_AUTH_INFO = "/wxapi/card/invoice/getbatchinsertauthinfo?access_token=";


	/**
	 * 微信侧获取用户一次授权信息
	 */
	public String checkBatchAuthInfo(String partyId, String appId, String source) throws BaseAppException {

		// 获取access_token
		String accessToken = getSelWxAccessToken();

		NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
		String url = nacosCommon.getProxyUrl()+WX_GET_BATCH_AUTH_INFO + accessToken;
		LOGGER.info("获取用户一次授权信息请求 URL: {}", url);

		JSONObject param = new JSONObject();
		param.put("out_user_id", partyId);  // 商户侧用户唯一标识

		LOGGER.info("获取用户一次授权信息入参: {}", param);
		String res = HttpUtil.postJSONHttpProxy(url, param.toString(), HttpConstant.CONNECT_TIMEOUT, PROXY_FLAG);
		LOGGER.info("获取用户一次授权信息出参: {}", res);

		if (StringUtils.isEmpty(res)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_BATCHAUTH_INFO_FALIL);
		}

		JSONObject resObj = JSON.parseObject(res);
		String errCode = resObj.getString(ERRCODE);

		if (!StringUtils.equals("0", errCode)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_BATCHAUTH_INFO_FALIL);
		}

		// 0：没有授权，1：有授权
		String hasAuth = resObj.getString("has_auth");

		// 用户授权的过期时间戳，当前时间超过这个时间戳，授权就过期了
		int expireTimestamp = resObj.getIntValue("expire_timestamp");

		String openId = resObj.getString("openid");

		// 加锁
		String lockValue = UUIDHelper.getUUID();
		String lockKey = InvoiceConstant.INIT_USER_INFO_PREFIX + partyId;
		String lockResult = RedisUtil.waitForTryLock(redisOperation, "查询用户授权信息", lockKey, lockValue);

		// 获取锁成功
		if (StringUtils.equals(lockResult,RedisUtil.LOCK_SUCCESS)) {

			// 可能会没有用户信息
			User userInfo = this.checkInfoMapper.selectUserByPartyId(partyId);
			if (null == userInfo) {
				User user = new User(partyId, hasAuth, new Date());
				this.checkInfoMapper.insertUserInfos(user);
			} else {
				// 更新用户信息表用户微信授权状态
				this.checkInfoMapper.updateUserWxAuth(partyId, hasAuth);
			}

			// 授权信息新增/更新至用户授权信息表
			int record = this.checkInfoMapper.selectUserAuthByPartyIdAndAppid(partyId, appId);

			UserAuthDTO userAuthDTO = new UserAuthDTO(partyId, appId, openId, hasAuth, source, expireTimestamp);

			if (record <= 0) {
				// 新增用户在appid内授权信息
				this.checkInfoMapper.insertUserAuthInfo(userAuthDTO);

			} else {
				// 更新用户在appid内授权信息
				this.checkInfoMapper.updateUserAuthInfo(userAuthDTO);

			}

			RedisUtil.unlock(redisOperation, "查询用户授权信息", lockKey, lockValue);
		}

		JSONObject result = new JSONObject();
		result.put("hasAuth",hasAuth);
		result.put("expDate",expireTimestamp);

		return result.toString();
	}

	/**
	 * ticketId同步订单号,用订单号查询出开具的发票详情
	 */
	public void syncTicketIdWithOrderId(String ticketId, String orderId) {
		//什么时候，什么地方同步此信息?
		this.checkInfoMapper.updateInvoiceAuthOrderIdByTicketId(ticketId, orderId);
	}

	/**
	 * 微信侧查询用户授权状态
	 */
//	private static final String WX_GET_TICKET_AUTH_URL = "https://api.weixin.qq.com/card/invoice/getauthdata?access_token=";
	private static final String WX_GET_TICKET_AUTH_URL = "/wxapi/card/invoice/getauthdata?access_token=";

	/**
	 * 用于提交开票申请/获取授权url状态后后触发
	 * 1.type=0 获取授权页至微信侧用的是ticketId,获取授权需要用ticketId和orderId关联
	 * 2.type=2 获取授权页至微信侧用的是发票实例id
	 */
	public String checkTicketAuthInfo(String ticketId, String invoiceId, String type) throws BaseAppException {

		// 获取access_token
		String accessToken = getSelWxAccessToken();

		// 获取商户开票平台标识
		String sPappid = getSPappid();

		String url = WX_GET_TICKET_AUTH_URL + accessToken;
		LOGGER.info("查询授权完成状态 URL:{}", url);

		JSONObject param = new JSONObject();

		if (StringUtils.equals("0", type)) {
			param.put("order_id", ticketId);
		}

		if (StringUtils.equals("2", type)) {
			param.put("order_id", invoiceId);
		}

		param.put("s_pappid", sPappid);

		LOGGER.info("查询授权完成状态 入参: {}", param);
		NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
		String res = HttpClientUtils.sendPost(nacosCommon.getProxyUrl()+url, param.toString(), USE_PROXY);
		LOGGER.info("查询授权完成状态 出参: {}", res);

		if (StringUtils.isEmpty(res)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_GET_TICKET_AUTH_FALIL);
		}

		JSONObject resObj = JSON.parseObject(res);
		String errCode = resObj.getString(ERRCODE);
		String authStatus = resObj.getString("invoice_status");

		// 授权成功,只更新授权状态,定时任务处理后续插卡操作
		if (StringUtils.equals("0", errCode) && StringUtils.equals("auth success", authStatus)) {

			// 此场景微信侧授权成功,可能本地还未开具成功
			if (StringUtils.equals("0", type)) {
				this.checkInfoMapper.updateInvoiceAuthWxByTicketId(ticketId, "1");
			}

			// 此场景下判断发票是否是开具完成的状态
			if (StringUtils.equals("2", type)) {
				this.checkInfoMapper.updateInvoiceAuthWxByInvoiceId(invoiceId, "1");
			}

			String openId = resObj.getString("openid");
			LOGGER.info("查询授权完成状态 :{} openid:{}", authStatus, openId);

			// 通过invoiceId更新openid到订单表
			this.checkInfoMapper.updateInvoiceOrderWxOpenId(invoiceId, openId);

		}

		// 授权失败、未授权 更新授权信息为未授权如何处理？
		return res;

	}

	/**
	 * 通过授权成功接口查询已授权进入此方法,将电子发票卡券插入用户卡包一系列操作
	 * type：公众号插卡为null
	 *       一次授权多次插卡为once
	 */
	public void toInsertInvoice(String invoiceId,String partyId,String appId,String type) throws BaseAppException {

		InvoiceDetail invoiceDetail = null;

		if (StringUtils.isEmpty(type)) { //公众号开票
			invoiceDetail = this.checkInfoMapper.selectInvoiceById(invoiceId);

		} else { //app开票
			invoiceDetail = this.checkInfoMapper.selectInvoiceByPartyIdOnceAuth(invoiceId,partyId,appId);
		}

		if (null == invoiceDetail) {
			LOGGER.info("未查询到发票【{}】的信息", invoiceId);
			return;
		}

		// 判断用户发票是否已经插入到卡包,已经插卡成功,不能再次插入  wxCardInsert=1插卡
		String wxCardInsert = invoiceDetail.getWxCardInsert();
		if (StringUtils.equals("1", wxCardInsert)) { 
			LOGGER.info("发票【{}】已经插入卡包,无法再次插入", invoiceId);
			return;
		}

		// 一次授权插卡时校验用户是否超过授权插卡阈值 单日50 总次数500
		if (!StringUtils.isEmpty(type)) {
			boolean canInsert = this.checkCardInsert(invoiceDetail);
			if (!canInsert) {
				partyId = invoiceDetail.getPartyId();
				LOGGER.info("用户【{}】微信侧插卡受限，无法插卡", partyId);
				return;
			}
		}

		// 创建卡券模板
		createCard(invoiceDetail);

		// 上传pdf
		uploadInvoicePdf(invoiceDetail);

		// 插卡操作
		insertInvoice(invoiceDetail, type);

	}

	/**
	 * 用户当天插入次数
	 */
	private static final String WX_DAY_CARD_INSERT_KEY = "DAY_CARD_INSERT_";

	/**
	 * 校验用户插卡次数
	 * 1.当日是否超过50次
	 * 2.总数是否超过500
	 * 3.授权是否到期
	 *
	 * @return 是否可以插卡 false不可以 true可以
	 */
	public boolean checkCardInsert(InvoiceDetail invoiceDetail) throws BaseAppException {

		String partyId = invoiceDetail.getPartyId();
		String wxAuth = invoiceDetail.getWxAuth();

		// 微信侧插卡受限后此状态会更新，插卡前判断授权状态
		if (!StringUtils.equals("1", wxAuth)) {
			LOGGER.info("用户{}微信侧一次授权状态:{}", partyId, wxAuth);
			return false;
		}

		// 总次数超过500次
		int totalInsert = this.checkInfoMapper.selectWxUserTotalInsert(partyId);

		LOGGER.info("用户{}微信侧插卡总次数:{}", partyId, totalInsert);

		if (totalInsert >= 500) {
			return false;
		}

		String key = WX_DAY_CARD_INSERT_KEY + partyId;
		String times = this.redisOperation.getForString(key);
		LOGGER.info("用户{}微信侧当天插卡总次数: {}", partyId, times);

		if (StringUtils.isEmpty(times)) {
			// 失效时间为当前时间至当天结束时间
			int exTime = DateUtils.nowToEndSecond();
			this.redisOperation.setEx(key, exTime, "0");
		} else {
			int count = Integer.parseInt(times);
			// 当日超过50次，无法插卡，更新授权状态
			if (count >= 50) {
				String appId = invoiceDetail.getAppId();
				String source = invoiceDetail.getSource();
				// 调用微信侧接口查看授权状态,并更新
				this.checkBatchAuthInfo(partyId, appId, source);
				return false;
			}
		}

		return true;

	}

	/**
	 * 微信侧创建卡券模板接口url
	 */
//	private static final String WX_INVOICE_CREATECARD_URL = "https://api.weixin.qq.com/card/invoice/platform/createcard?access_token=";
	private static final String WX_INVOICE_CREATECARD_URL = "/wxapi/card/invoice/platform/createcard?access_token=";

	/**
	 * 创建发票卡券模版
	 */
	public void createCard(InvoiceDetail invoiceDetail) throws BaseAppException {

		// 同一个payee 只需要一个卡券模板
		String cardId = this.checkInfoMapper.selectCardIdByAppid(APP_ID);

		if (!StringUtils.isEmpty(cardId)) {
			invoiceDetail.setCardId(cardId);
			this.checkInfoMapper.updateInvoiceCardIdById(invoiceDetail.getOrderId(),invoiceDetail.getInvoiceId(), cardId);
			return;
		}

		// 获取access_token
		String accessToken = getSelWxAccessToken();

		NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
		String url = nacosCommon.getProxyUrl()+WX_INVOICE_CREATECARD_URL + accessToken;
		LOGGER.info("微信创建卡券模板接口 URL:{}", url);

		// 根据appid查询数据库中模板信息
		BaseInvoiceTemplate baseInvoiceTemplate = this.checkInfoMapper.selectCardTemplateByAppid(APP_ID);
		String param = baseInvoiceTemplate.toString();

		LOGGER.info("微信创建卡券模板接口 入参:{}", param);
		String res = EInvoiceUtil.postJSONHttpProxy(url, param, HttpConstant.CONNECT_TIMEOUT, PROXY_FLAG);
		LOGGER.info("微信创建卡券模板接口 出参:{}", res);

		if (StringUtils.isEmpty(res)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_CREATECARD_FALIL);
		}

		JSONObject resObj = JSONObject.parseObject(res);
		String errCode = resObj.getString(ERRCODE);

		if (!StringUtils.equals("0", errCode)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_CREATECARD_FALIL);
		}

		cardId = resObj.getString("card_id");
		// 卡券模板id更新到发票信息和商户信息中
		invoiceDetail.setCardId(cardId);
		this.checkInfoMapper.updateInvoiceCardIdById(invoiceDetail.getOrderId(),invoiceDetail.getInvoiceId(), cardId);
		this.checkInfoMapper.updateCardIdByAppid(APP_ID, cardId);

	}

	/**
	 * 微信侧上传电子发票PDF接口url
	 */
//	private static final String WX_INVOICE_UPLOAD_PDF_URL = "https://api.weixin.qq.com/card/invoice/platform/setpdf?access_token=";
	private static final String WX_INVOICE_UPLOAD_PDF_URL = "/wxapi/card/invoice/platform/setpdf?access_token=";

	/**
	 * 上传pdf
	 */
	public void uploadInvoicePdf(InvoiceDetail invoiceDetail) throws BaseAppException {

		String sMediaId = invoiceDetail.getsMediaId();

		// 已经上传过直接返回,sMediaId 3天内有效
		if (!StringUtils.isEmpty(sMediaId)) {
			return;
		}

		// PDF下载地址
		String pdfUrl = invoiceDetail.getPdfUrl();

		if (StringUtils.isEmpty(pdfUrl)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_PDFURL_EMPTY);
		}

		// 从pdfUrl下载发票对应PDF文件至临时文件夹再调用接口上传PDF
		String fileName = DateHelper.getCurrentTimeStampString() + ".pdf";
		String uuid = UUIDHelper.getUUID() + "/";
		String savePath = Objects.requireNonNull(WxService.class.getClassLoader().getResource("/")).getPath();
		savePath = savePath.replace("classes","INVOICE_PDF") + uuid ;
		// 文件路径
		LOGGER.info("文件夹路径:{}", savePath);
		String realFilePath = null;
		try {
			realFilePath = EInvoiceUtil.downLoadFromUrl(pdfUrl, fileName, savePath);
		} catch (IOException e) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_PDFDOWNLOAD_FALIL);
		}
		LOGGER.info("文件{}下载成功! 文件路径:{}", fileName, realFilePath);

		// String filePath = "/home/vmuser/tomcat/tomcat_uqc/tomcat_1/webapps/uqc-main/WEB-INF/INVOICE_PDF/1.pdf";

		// PDF下载失败
		if (StringUtils.isEmpty(realFilePath)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_PDFDOWNLOAD_FALIL);
		}

		// 获取access_token
		String accessToken = getSelWxAccessToken();

		NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
		String url = nacosCommon.getProxyUrl()+WX_INVOICE_UPLOAD_PDF_URL + accessToken;
		LOGGER.info("微信上传电子发票PDF接口 URL:{}", url);

		String res = null;

		try {
			res = EInvoiceUtil.uploadFile(url, realFilePath, USE_PROXY);
		} catch (IOException e) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_UPLOADPDF_FALIL);
		}

		LOGGER.info("微信上传电子发票PDF接口 出参:{}", res);

		if (StringUtils.isEmpty(res)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_UPLOADPDF_FALIL);
		}

		JSONObject resObj = JSONObject.parseObject(res);
		String errCode = resObj.getString(ERRCODE);

		if (!StringUtils.equals("0", errCode)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_UPLOADPDF_FALIL);
		}

		sMediaId = resObj.getString("s_media_id");
		invoiceDetail.setsMediaId(sMediaId);

		// sMediaId更新至详情中
		this.checkInfoMapper.updateInvoiceMediaIdById(invoiceDetail.getOrderId(),invoiceDetail.getInvoiceId(), sMediaId);
	}

	/**
	 * 微信侧插卡接口URL
	 */
//	private static final String WX_INVOICE_CARD_INSERT_URL = "https://api.weixin.qq.com/card/invoice/insert?access_token=";
	private static final String WX_INVOICE_CARD_INSERT_URL = "/wxapi/card/invoice/insert?access_token=";

	/**
	 * 微信侧一次授权插卡url
	 */
//	private static final String WX_INVOICE_CARD_BATCH_INSERT_URL = "https://api.weixin.qq.com/card/invoice/batchinsertauthinsert?access_token=";
	private static final String WX_INVOICE_CARD_BATCH_INSERT_URL = "/wxapi/card/invoice/batchinsertauthinsert?access_token=";


	/**
	 * 电子发票卡券插入用户卡包
	 */
	public void insertInvoice(InvoiceDetail invoiceDetail, String type) throws BaseAppException {

		String cardId = invoiceDetail.getCardId();
		// 没有创建卡券模板id
		if (StringUtils.isEmpty(cardId)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_CARDINSERT_PARAM_ERROR);
		}

		String sMediaId = invoiceDetail.getsMediaId();
		// 没有上传pdf文件
		if (StringUtils.isEmpty(sMediaId)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_CARDINSERT_PARAM_ERROR);
		}

		// 插卡入参
		BaseInvoiceCardTemplate cardTemplate = new BaseInvoiceCardTemplate();

		String invoiceId = invoiceDetail.getInvoiceId();
		String money = invoiceDetail.getMoney();
		String title = invoiceDetail.getTitleName();
		Date billingTime = invoiceDetail.getInvoiceDate();
		String billingCode = invoiceDetail.getInvoiceCode();
		String billingNo = invoiceDetail.getInvoiceNo();
		String checkCode = invoiceDetail.getCheckCode();
		String openId = invoiceDetail.getWxOpenId();
		String partyId = invoiceDetail.getPartyId();
		String appId = invoiceDetail.getAppId();
        String areaCode = invoiceDetail.getAreaCode();

		// 发票信息校验
		if (StringUtils.isEmpty(billingCode) || StringUtils.isEmpty(billingNo) || StringUtils.isEmpty(checkCode)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_INFO_EMPTY_ERROR);
		}

		String tax = invoiceDetail.getTax();

		// 政企用户参数
		// 购买方纳税人识别号
		String buyerNumber = invoiceDetail.getTaxNum();
		// 购买方地址电话
		String buyerAddressAndPhone = invoiceDetail.getCompanyAddr() + invoiceDetail.getCompanyPhone();
		// 购买方开户行及账号
		String buyerBankAccount = invoiceDetail.getBankName() + invoiceDetail.getBankAccount();
		// 备注
		String remarks = invoiceDetail.getRemark();
		// 订单号
        String orderId = invoiceId + areaCode;


		cardTemplate.setPartyId(partyId);
		cardTemplate.setWxOpenId(openId);
		cardTemplate.setOrderId(orderId);
		cardTemplate.setCardId(cardId);
		cardTemplate.setFee(Integer.parseInt(money));
		cardTemplate.setTitle(title);
		cardTemplate.setBillingTime(DateUtils.dateToTimestamp(billingTime));
		// 企信侧与微信侧相反
		cardTemplate.setBillingNo(billingCode);
		cardTemplate.setBillingCode(billingNo);

		cardTemplate.setFeeWithoutTax(Integer.parseInt(money) - Integer.parseInt(tax));
		cardTemplate.setCheckCode(checkCode);
		cardTemplate.setTax(Integer.parseInt(tax));
		cardTemplate.setsPdfMediaId(sMediaId);
		cardTemplate.setBuyerNumber(buyerNumber);
		cardTemplate.setBuyerAddressAndPhone(buyerAddressAndPhone);
		cardTemplate.setBuyerBankAccount(buyerBankAccount);
		cardTemplate.setRemarks(remarks);

		String url;
		NacosCommon nacosCommon = SpringUtil.getBean("nacosCommon");
		if (StringUtils.isEmpty(type)) {
			cardTemplate.setAppid(APP_ID_ID);
			url = nacosCommon.getProxyUrl()+WX_INVOICE_CARD_INSERT_URL;
		} else {
			cardTemplate.setAppid(appId);
			url = nacosCommon.getProxyUrl()+WX_INVOICE_CARD_BATCH_INSERT_URL;
		}

		String param = cardTemplate.toString();

		// 插卡
		String accessToken = getSelWxAccessToken();
		url += accessToken;

		LOGGER.info("微信电子发票卡券插入用户卡包接口 入参:{}", param);
		String res = EInvoiceUtil.postJSONHttpProxy(url, param, HttpConstant.CONNECT_TIMEOUT, PROXY_FLAG);
		LOGGER.info("微信电子发票卡券插入用户卡包接口 出参:{}", res);

		if (StringUtils.isEmpty(res)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_CARDINSERT_FALIL);
		}
		// 保存插卡次数
		saveInsertCount(invoiceDetail.getInvoiceId(), partyId, invoiceDetail.getOrderId());

		JSONObject resObj = JSONObject.parseObject(res);
		String errCode = resObj.getString(ERRCODE);

		if (!StringUtils.equals("0", errCode)) {
			ExceptionUtil.throwBusiException(ErrorConstants.WX_INVOICE_CARDINSERT_FALIL);
		}

		// 一次授权插卡成功 次数增加
		if (!StringUtils.isEmpty(type)) {
			String key = WX_DAY_CARD_INSERT_KEY + partyId;
			this.redisOperation.incr(key);

			// 更新总次数
			this.checkInfoMapper.updateUserWxCardInsert(partyId);
		}

		// 发票code点击发票跳转带上,标识发票 || 发票用户openid
		String code = resObj.getString("code");

		// 插卡成功更新detail表插卡状态,以及微信册card对应的encrypt_code || unionId更新至用户信息表中？ 微信侧 用户唯一标识unionId
		this.checkInfoMapper.updateWxCardInsertByInvoiceId(invoiceDetail.getOrderId(),invoiceDetail.getInvoiceId(), "1", code);
		// 非一次授权状态下,插卡成功后更新auth表状态为插卡成功
		this.checkInfoMapper.updateInvoiceAuthStatus(invoiceDetail.getId(), "2");

	}

	public void saveInsertCount(String invoiceId, String partyId, String orderId) {
		Integer insertCount = checkInfoMapper.selectInvoiceInsertCount(orderId, invoiceId);
		if(insertCount != null && insertCount>0){
			checkInfoMapper.updateInvoiceInsertCount(orderId, invoiceId,insertCount+1);
		}else {
			InvoiceInsert invoiceInsert = new InvoiceInsert();
			invoiceInsert.setInsertCount(1);
			invoiceInsert.setInvoiceId(invoiceId);
			invoiceInsert.setPartyId(partyId);
			invoiceInsert.setOrderId(orderId);
			checkInfoMapper.insertWxInvoiceInsert(invoiceInsert);
		}
	}

}
